/* 
ToDo:
	1) 需要测试start_tx和stop_tx是否能停止和继续
*/
#include <uart.h>
#include <arch/io.h>
#include <arch/cpu/soc/s3c2440.h>
#include <irq.h>
#include <arch/cpu/soc/irq.h>
#include <clk.h>
#include <errno.h>
#include <config.h>
#include <device.h>

#define S3C2440_UART_BUFSIZE	(512) 

struct s3c2440_uart_priv {
	unsigned int index;
	struct clk *pclk;
	struct clk *clk_gpio;
	struct clk *clk_uart;
	unsigned int baudrate;
	unsigned int rx_irq;
	unsigned int tx_irq;
	unsigned int recv_head;
	unsigned int recv_tail;
	unsigned int xmit_head;
	unsigned int xmit_tail;
	struct char_with_flags recv_buf[S3C2440_UART_BUFSIZE];
	struct char_with_flags xmit_buf[S3C2440_UART_BUFSIZE];
};

static struct s3c2440_uart_priv s3c2440_uart0_priv = {
	.index = 0,
};
static struct s3c2440_uart_priv s3c2440_uart1_priv = {
	.index = 1,
};

static struct s3c2440_uart_priv s3c2440_uart2_priv = {
	.index = 2,
};

static struct irqaction s3c2440_uart_rx_irqaction;
static struct irqaction s3c2440_uart_tx_irqaction;

static int s3c2440_uart_is_tx_full(struct uart *uart)
{
	struct s3c2440_uart_priv *priv = uart->priv;
	unsigned int tmp;
	
	tmp = (priv->xmit_tail + 1) & (S3C2440_UART_BUFSIZE - 1);
	return (tmp == priv->xmit_head);
}

static void s3c2440_uart_start_tx(struct uart *uart)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	struct s3c2440_uart_priv *priv = uart->priv;
	uint32_t tmp;

	tmp  = readl(&uart_regs->ucon);
	tmp &= ~(3 << 2);
	tmp |= (1 << 2);
	writel(tmp, &uart_regs->ucon);

	enable_irq(priv->tx_irq);
}

static void s3c2440_uart_stop_tx(struct uart *uart)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	struct s3c2440_uart_priv *priv = uart->priv;
	uint32_t tmp;

	tmp  = readl(&uart_regs->ucon);
	tmp &= ~(3 << 2);
	writel(tmp, &uart_regs->ucon);

	disable_irq(priv->tx_irq);
}

static int s3c2440_uart_is_tx_empty(struct uart *uart)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	struct s3c2440_uart_priv *priv = uart->priv;
	
	return ((priv->xmit_tail == priv->xmit_head) && (((readl(&uart_regs->ufstat) >> 8) & 0x3f) == 0));
}
    
static void s3c2440_uart_start_rx(struct uart *uart)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	struct s3c2440_uart_priv *priv = uart->priv;
	uint32_t tmp;

	tmp  = readl(&uart_regs->ucon);
	tmp &= ~(3 << 0);
	tmp |= (1 << 0);
	writel(tmp, &uart_regs->ucon);

	enable_irq(priv->rx_irq);
}

static void s3c2440_uart_stop_rx(struct uart *uart)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	struct s3c2440_uart_priv *priv = uart->priv;
	uint32_t tmp;

	tmp  = readl(&uart_regs->ucon);
	tmp &= ~(3 << 0);
	writel(tmp, &uart_regs->ucon);

	disable_irq(priv->rx_irq);
}

static int s3c2440_uart_is_rx_empty(struct uart *uart)
{
	struct s3c2440_uart_priv *priv = uart->priv;
	
	return (priv->recv_tail == priv->recv_head);
}

static irqreturn_t s3c2440_uart_rx_irq(unsigned int irq, void *_priv)
{
	struct uart *uart = _priv;
	struct tty *tty = uart_to_tty(uart);
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	struct s3c2440_uart_priv *priv = uart->priv;
	struct tty_ldisc *ld;
	unsigned int tmp1 = 0, tmp, recv = 0;
	unsigned char fg, ch;
	
    ld = tty_ldisc_get(tty);     
    if (ld && !IS_ERR(ld) && tty_flags_is_ldisc_enabled(tty->flags) && ld->ops.receive)
		recv = 1;

	while (readl(&uart_regs->ufstat) & 0x3f) {
		if (!recv) {
			tmp1 = (priv->recv_tail + 1) & (S3C2440_UART_BUFSIZE - 1);
			if (tmp1 == priv->recv_head) 
				break;
		}

		fg = 0;
		tmp = readl(&uart_regs->uerstat);			
		if (tmp & (1 << 3)) 
			fg = fg | UART_CHAR_BREAK;
		if (tmp & 0x7)
			fg = fg | UART_CHAR_ERROR;
		ch = readb(&uart_regs->urxh);

		if (recv) {
			ld->ops.receive(tty, &ch, &fg, 1);
		} else {
			priv->recv_buf[priv->recv_tail].fg = fg;
			priv->recv_buf[priv->recv_tail].ch = ch;			
			priv->recv_tail = tmp1;
		}
	}
	
	return IRQ_HANDLED;
}

static irqreturn_t s3c2440_uart_tx_irq(unsigned int irq, void *_priv)
{
	struct uart *uart = _priv;
	struct s3c2440_uart_priv *priv = uart->priv;
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	
	while (priv->xmit_head != priv->xmit_tail) {
		if (readl(&uart_regs->ufstat) & (1 << 14)) 		
			break;
		writeb(priv->xmit_buf[priv->xmit_head].ch, &uart_regs->utxh);
		priv->xmit_head = (priv->xmit_head + 1) & (S3C2440_UART_BUFSIZE - 1);		
	}

	return IRQ_HANDLED;
}

static int s3c2440_uart_startup(struct uart *uart)
{
	struct s3c2440_uart *uart_regs;
	struct s3c2440_gpio *gpio = s3c2440_get_base_gpio();
	struct s3c2440_uart_priv *priv = uart->priv;
	struct clk *clk;
	int ret;
	uint32_t tmp;

	switch (priv->index) {
		case 0:
			uart->membase = s3c2440_get_base_uart(0);
			break;
		case 1:
			uart->membase = s3c2440_get_base_uart(1);
			break;
		case 2:
			uart->membase = s3c2440_get_base_uart(2);
			break;
		default:
			return -ENODEV;
	}
	uart_regs = (struct s3c2440_uart *)uart->membase;

	clk = clk_get("pclk");
	ret = clk_enable(clk);
	if (IS_ERR(ret)) 
		return ret;
	priv->pclk = clk;

	clk = clk_get("gpio");
	ret = clk_enable(clk);
	if (IS_ERR(ret)) 
		goto err_clk_gpio;
	priv->clk_gpio = clk;

	clk = NULL;
	switch (priv->index) {
		case 0:
			clk = clk_get("uart0");
			break;
		case 1:
			clk = clk_get("uart1");
			break;
		case 2:
			clk = clk_get("uart2");
			break;
	}
	ret = clk_enable(clk);
	if (IS_ERR(ret)) 
		goto err_clk_uart;
	priv->clk_uart = clk;		

	tmp  = readl(&gpio->gphcon) & ~(0xf << (4 + priv->index * 4));
	tmp |= (0xa << (4 + priv->index * 4));
	writel(tmp, &gpio->gphcon);
	tmp  = readl(&gpio->gphup) & ~(3 << (2 + priv->index * 2));
	writel(tmp, &gpio->gphup);
	
	writel(0x07, &uart_regs->ufcon);		// FIFO enable, reset Tx/Rx FIFO
	if (priv->index != 2) {
		writel(0, &uart_regs->umcon);	// no auto flow control
	}
	writel(0x3, &uart_regs->ulcon);		// no parity, 1 stop, 8 bit
	writel(0x5, &uart_regs->ucon);		// interrupt/polling mode, pulse mode, PCLK for baudrate

	tmp = clk_get_rate(priv->pclk);
	if (!tmp) {
		ret = -ENODEV;
		goto err_set_baud;
	}
	tmp = tmp / (16 * CONFIG_DEFAULT_BAUDRATE) - 1;
	if (tmp >> 16) {
		ret = -EINVAL;
		goto err_set_baud;
	}
	writel(tmp, &uart_regs->ubrdiv);
	priv->baudrate = CONFIG_DEFAULT_BAUDRATE;		

	priv->xmit_head = priv->xmit_tail = 0;
	priv->recv_head = priv->recv_tail = 0;

	switch (priv->index) {
		case 0:
			priv->rx_irq = IRQ_RXD0;
			priv->tx_irq = IRQ_TXD0;
			break;
		case 1:
			priv->rx_irq = IRQ_RXD1;
			priv->tx_irq = IRQ_TXD1;
			break;
		case 2:
			priv->rx_irq = IRQ_RXD2;
			priv->tx_irq = IRQ_TXD2;
			break;
	}
	ret = request_irq(priv->rx_irq, &s3c2440_uart_rx_irqaction, s3c2440_uart_rx_irq, 0, uart);
	if (IS_ERR(ret)) 
		goto err_rx_irq;
		
	ret = request_irq(priv->tx_irq, &s3c2440_uart_tx_irqaction, s3c2440_uart_tx_irq, 0, uart);
	if (IS_ERR(ret))
		goto err_tx_irq;
	
	return 0;

err_tx_irq:
	free_irq(priv->rx_irq, uart);
err_rx_irq:
err_set_baud:
	clk_disable(priv->clk_uart);
err_clk_uart:
	clk_disable(priv->clk_gpio);
err_clk_gpio:
	clk_disable(priv->pclk);

	return ret;
}

static void s3c2440_uart_shutdown(struct uart *uart)
{
	struct s3c2440_uart_priv *priv = uart->priv;

	clk_disable(priv->clk_uart);
	clk_disable(priv->clk_gpio);
	clk_disable(priv->pclk);
	free_irq(priv->rx_irq, &uart);
	free_irq(priv->tx_irq, &uart);
}

static int s3c2440_uart_get_data_bits(struct uart *uart)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	uint32_t tmp;

	tmp = readl(&uart_regs->ulcon);
	switch (tmp & 0x3) {
		case 0:
			return 5;
		case 1:
			return 6;
		case 2:
			return 7;
		case 3:
			return 8;
	}	
	return 0;
}

static int s3c2440_uart_set_data_bits(struct uart *uart, uint8_t bits)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	uint32_t tmp;

	if ((bits < 5) || (bits > 8))
		return -EINVAL;
		
	tmp  = readl(&uart_regs->ulcon);
	tmp &= ~3;
	switch (bits) {
		case 5:
			break;
		case 6:
			tmp |= 1;
			break;
		case 7:
			tmp |= 2;
			break;
		case 8:
			tmp |= 3;
			break;
	}
	writel(tmp, &uart_regs->ulcon);
	return 0;
}

static int s3c2440_uart_get_stop_bits(struct uart *uart)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	uint32_t tmp;

	tmp = readl(&uart_regs->ulcon);
	if (tmp & (1 << 2)) {
		return 2;
	} else {
		return 1;
	}	
	return 0;
}

static int s3c2440_uart_set_stop_bits(struct uart *uart, uint8_t bits)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	uint32_t tmp;

	if ((bits < 1) || (bits > 2))
		return -EINVAL;
		
	tmp  = readl(&uart_regs->ulcon);
	if (bits == 1) 
		tmp &= ~(1 << 2);
	else
		tmp |= (1 << 2);
	writel(tmp, &uart_regs->ulcon);
	return 0;
}

static int s3c2440_uart_get_baudrate(struct uart *uart)
{
	struct s3c2440_uart_priv *priv = uart->priv;

	return priv->baudrate;
}

static int s3c2440_uart_set_baudrate(struct uart *uart, uint32_t baud)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	struct s3c2440_uart_priv *priv = uart->priv;
	uint32_t tmp;
	
	if (baud == 0)
		return -EINVAL;
		
	tmp = clk_get_rate(priv->pclk);
	if (!tmp) 
		return -EFAULT;
		
	tmp = tmp / (16 * baud) - 1;
	if (tmp >> 16) 
		return -EINVAL;
		
	writel(tmp, &uart_regs->ubrdiv);
	priv->baudrate = baud;
	return 0;
}

static int s3c2440_uart_get_parity_bit(struct uart *uart)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	uint32_t tmp;

	tmp = readl(&uart_regs->ulcon);
	switch ((tmp & (7 << 3)) >> 3) {
		case 0:
			return UART_PARITY_NO;
		case 4:
			return UART_PARITY_ODD;
		case 5:
			return UART_PARITY_EVEN;
		default:
			return -EFAULT;
	}	
	return 0;
}

static int s3c2440_uart_set_parity_bit(struct uart *uart, uint8_t parity)
{
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	uint32_t tmp;

	tmp  = readl(&uart_regs->ulcon);
	tmp &= ~(7 << 3);
	switch (parity) {
		case UART_PARITY_NO:
			break;
		case UART_PARITY_ODD:
			tmp |= 4;
			break;
		case UART_PARITY_EVEN:
			tmp |= 5;
			break;
		default:
			return -EINVAL;
	}
	writel(tmp, &uart_regs->ulcon);
	return 0;
}
    
static int s3c2440_uart_send_char(struct uart *uart, unsigned char ch, unsigned char fg)
{
	struct s3c2440_uart_priv *priv = uart->priv;
	struct s3c2440_uart *uart_regs = (struct s3c2440_uart *)uart->membase;
	unsigned int flags;

	if (fg) 
		return -ENOTSUPP;
		
	if (s3c2440_uart_is_tx_full(uart)) 
		return -EIO;
		
	if (s3c2440_uart_is_tx_empty(uart)) {
		writeb(ch, &uart_regs->utxh);
	} else {
		local_irq_save(flags);
		priv->xmit_buf[priv->xmit_tail].ch = ch;
		priv->xmit_buf[priv->xmit_tail].fg = 0;
		priv->xmit_tail = (priv->xmit_tail + 1) & (S3C2440_UART_BUFSIZE - 1);		
		local_irq_restore(flags);
	}

	if (ch == '\n') {
		return s3c2440_uart_send_char(uart, '\r', 0);
	}
	return 0;
}

static int s3c2440_uart_recv_char(struct uart *uart, unsigned char *ch, unsigned char *fg)
{
	struct s3c2440_uart_priv *priv = uart->priv;
	
	if (s3c2440_uart_is_rx_empty(uart)) 
		return -EIO;

	*ch = priv->recv_buf[priv->recv_head].ch;
	*fg = priv->recv_buf[priv->recv_head].fg;
	priv->recv_head = (priv->recv_head + 1) & (S3C2440_UART_BUFSIZE - 1);
	return 0;
}
    
static struct uart_operations s3c2440_uart_ops = {
	.is_tx_full		= s3c2440_uart_is_tx_full,
	.stop_tx		= s3c2440_uart_stop_tx,
	.start_tx		= s3c2440_uart_start_tx,
	.is_tx_empty	= s3c2440_uart_is_tx_empty,
	.is_rx_empty	= s3c2440_uart_is_rx_empty,
	.start_rx		= s3c2440_uart_start_rx,
	.stop_rx		= s3c2440_uart_stop_rx,
	.startup		= s3c2440_uart_startup,
	.shutdown		= s3c2440_uart_shutdown,
	.get_data_bits	= s3c2440_uart_get_data_bits,
	.set_data_bits	= s3c2440_uart_set_data_bits,
	.get_stop_bits	= s3c2440_uart_get_stop_bits,
	.set_stop_bits	= s3c2440_uart_set_stop_bits,
	.get_baudrate	= s3c2440_uart_get_baudrate,
	.set_baudrate	= s3c2440_uart_set_baudrate,
	.get_parity_bit	= s3c2440_uart_get_parity_bit,
	.set_parity_bit	= s3c2440_uart_set_parity_bit,
	.send_char		= s3c2440_uart_send_char,
	.recv_char		= s3c2440_uart_recv_char,
};

static struct uart s3c2440_uart0_uart;

DEVICE(s3c2440_uart0_device, "uart0", MKDEV(MAJOR_UART, 0), 
	DEV_FLAGS_CAN_READ | DEV_FLAGS_CAN_WRITE | DEV_FLAGS_CAN_NONBLOCK | DEV_FLAGS_CAN_SYNC, 
	&s3c2440_uart0_uart.tty);

static struct uart s3c2440_uart0_uart = {
	.tty = {
		.dev = &s3c2440_uart0_device,
		.ldisc_nr = -1,
	},
	.priv = &s3c2440_uart0_priv,
};

static struct uart s3c2440_uart1_uart;

DEVICE(s3c2440_uart1_device, "uart1", MKDEV(MAJOR_UART, 1), 
	DEV_FLAGS_CAN_READ | DEV_FLAGS_CAN_WRITE | DEV_FLAGS_CAN_NONBLOCK | DEV_FLAGS_CAN_SYNC, 
	&s3c2440_uart1_uart.tty);

static struct uart s3c2440_uart1_uart = {
	.tty = {
		.dev = &s3c2440_uart1_device,
		.ldisc_nr = -1,
	},
	.priv = &s3c2440_uart1_priv,
};

static struct uart s3c2440_uart2_uart;

DEVICE(s3c2440_uart2_device, "uart2", MKDEV(MAJOR_UART, 2), 
	DEV_FLAGS_CAN_READ | DEV_FLAGS_CAN_WRITE | DEV_FLAGS_CAN_NONBLOCK | DEV_FLAGS_CAN_SYNC, 
	&s3c2440_uart2_uart.tty);

static struct uart s3c2440_uart2_uart = {
	.tty = {
		.dev = &s3c2440_uart2_device,
		.ldisc_nr = -1,
	},
	.priv = &s3c2440_uart2_priv,
};

static struct uart_driver s3c2440_uart_uart_driver;

DRIVER(s3c2440_uart_driver, NULL, MKDEV(MAJOR_UART, 0), MKDEV(MAJOR_UART, 2), 
	&tty_ops, &s3c2440_uart_uart_driver.tdrv);

static struct uart_driver s3c2440_uart_uart_driver = {
	.tdrv = {
		.drv = &s3c2440_uart_driver,
		.ops = &uart_ops,
	},
	.ops = &s3c2440_uart_ops,
};

